#include "parser.h"
#include "globals.c"

enum token{nulsym = 1, identsym = 2, numbersym = 3, plussym = 4, minussym = 5, multsym = 6,
slashsym = 7, oddsym = 8, eqlsym = 9, neqsym = 10, lessym = 11, leqsym = 12,
gtrsym = 13, geqsym = 14, lparentsym = 15, rparentsym = 16, commasym = 17,
semicolonsym = 18, periodsym = 19, becomessym = 20, beginsym = 21, endsym = 22,
ifsym = 23, thensym = 24, whilesym = 25, dosym = 26, callsym = 27, constsym = 28,
intsym = 29, procsym = 30, outsym = 31, insym = 32, elsesym = 33};

enum opcode{
  lit = 1,
  opr = 2,
  lod = 3,
  sto = 4,
  cal = 5,
  inc = 6,
  jmp = 7,
  jpc = 8,
  sio = 9,
  sio2 = 10
};

enum oprcodes{
  ret = 0,
  neg,
  add,
  sub,
  mul,
  divi,
  odd,
  mod,
  eql,
  neq,
  lss,
  leq,
  gtr,
  geq
};

// Function prototypes
int splitByToken(char* targetStr, const char* token);
void printSymbolicRepresentation(FILE* outPtr, int lexLength);
void getSym(char* token);
void validateProgram();
void block();
void statement();
void condition();
void expression();
void term();
void factor();
void error(int errCode);
void emit(int op, int l, int m);

// Globals
char lexArr[MAX_PROGRAM_LENGTH][MAX_PROGRAM_LENGTH];
int tokenArray[MAX_PROGRAM_LENGTH];
char tokenNameArr[MAX_PROGRAM_LENGTH][MAX_PROGRAM_LENGTH];
int tokenIndex=0, tokenPtr=0;
int currentToken;

FILE* outputPtr;

symbol symTable[MAX_SYMBOL_TABLE_SIZE];
int symIndex=0;
int symAddr=3;

instruction *code;
int cx=0; // current assembly index

int parseLexemeList(FILE* outPtr, char* lexemeList, instruction* generatedCode){
  // print the symbolic representation of lexeme list
  code = generatedCode;
  outputPtr = outPtr;

  emit(inc, 0, 3);

  int lexLength = splitByToken(lexemeList, " ");
  printSymbolicRepresentation(outPtr, lexLength);

  // validate code
  validateProgram();

  emit(opr, 0, 0);
  // return length of assembly code if successful
  return cx+1;
}

void setNextToken(){
  currentToken = tokenArray[tokenPtr];
  tokenIndex = tokenPtr;
  tokenPtr++;
}

void printSymbolicRepresentation(FILE* outPtr, int lexLength){
  fprintf(outPtr, "\n\nSymbolic Representation:\n");
  int i, j=0, symType;
  for(i=0; i<lexLength; i++){
    symType = atoi(lexArr[i]);
    fprintf(outPtr, "%s ", symbolicNames[symType-1]);
    tokenArray[j] = symType;
    if(symType == identsym || symType == numbersym){
      i++;
      strcpy(tokenNameArr[j], lexArr[i]);
      fprintf(outPtr, "%s ", lexArr[i]);
    }
    j++;
  }
  fprintf(outPtr, "\n\n");
}

int splitByToken(char* targetStr, const char* token){
  token = strtok(targetStr, " ");
  int i=0;
  while(token != NULL){
    strcpy(lexArr[i], token);
    i++;
    token = strtok(NULL, " ");
  }
  return i;
}

void addSymbol(int kind, char* name, int val, int m, int l){
  char *tokenName;
  int ii,len;
  symIndex++;
  tokenName=name;
  len=strlen(tokenName);
  for(ii=0;ii<=len;ii++){
    symTable[symIndex].name[ii]=*tokenName; 
    tokenName++;
  }
  symTable[symIndex].kind=kind;
  if(kind==constsym){
    symTable[symIndex].addr=symAddr; 
    symTable[symIndex].val=val;
    symAddr++;
  }
  else if(kind==intsym){ 
    symTable[symIndex].level=l; 
    symTable[symIndex].addr=symAddr; 
    symTable[symIndex].val=0; // hw doesn't specify what uninitialized vars are
    // symTable[*ptx].indirect=flag; // what the actual fuck is this
    symAddr++;
  }
  else /* proc */
    symTable[symIndex].level=l;

  emit(inc, 0, 1);
}

void emit(int op, int l, int m){
  if(cx > MAX_CODE_LENGTH)
    error(NUM_TOO_LARGE);
  else{
    code[cx].op = op;
    code[cx].l = l;
    code[cx].m = m;
    cx++;
  }
}

int symbolAddrByName(char* symbolName){
  int i;
  for(i=1; i<=symIndex; i++){
    if(strcmp(symTable[i].name, symbolName) == 0){
      return symTable[i].addr;
    }
  }

  error(UNRECOGNIZED_ID);
}
int symbolValByName(char* symbolName){
  int i;
  for(i=1; i<=symIndex; i++){
    if(strcmp(symTable[i].name, symbolName) == 0){
      return symTable[i].val;
    }
  }

  error(UNRECOGNIZED_ID);
}

void error(int errCode){
  fprintf(outputPtr, "\n\n Error number %d: %s\n", errCode, errorMessages[errCode]);
  fclose(outputPtr);
  err(errCode, "ERROR: sym# %d which is %d throws %s\n", tokenIndex, currentToken, errorMessages[errCode]);
}

void validateProgram(){
  setNextToken();
  block();
  if(currentToken != periodsym) error(PERIOD_EXPECTED);
}

void block(){
  if(currentToken == constsym) {
      do{
          setNextToken();
          if(currentToken != identsym) error(CONST_INT_PROC_NFB_ID);
          char* constName = tokenNameArr[tokenIndex];
          setNextToken();
          if(currentToken != eqlsym) error(IDENT_NFB_EQUALS);
          setNextToken();
          if(currentToken != numbersym) error(EQUALS_NFB_NUM);
          int constVal = atoi(tokenNameArr[tokenIndex]);
          setNextToken();

          addSymbol(constsym, constName, constVal, 0, 0); //dunno if m or l are right

          emit(lit, 0, constVal);
          emit(sto, 0, symbolAddrByName(constName));
      }
      while(currentToken == commasym);
      if(currentToken != semicolonsym) error(SEMICOLON_COMMA_MISSING);
      setNextToken();
  }
  if(currentToken == intsym) {
      do{
          setNextToken();
          if(currentToken != identsym) error(CONST_INT_PROC_NFB_ID);
          char* intName = tokenNameArr[tokenIndex];
          setNextToken();

          addSymbol(intsym, intName, 0, 0, 0); //dunno if m or l are right
          emit(lit, 0, 0);
          emit(sto, 0, symbolAddrByName(intName));
      
      }
      while(currentToken == commasym);
      if(currentToken != semicolonsym) error(SEMICOLON_COMMA_MISSING);
      setNextToken();
  }
  while(currentToken == procsym){
    // HW3 GRAMMAR DOESN'T ALLOW FOR THIS.
      setNextToken();
      if(currentToken != identsym) error(CONST_INT_PROC_NFB_ID);
      setNextToken();
      if(currentToken != semicolonsym) error(SEMICOLON_COMMA_MISSING);
      setNextToken();
      block();
      if(currentToken != semicolonsym) error(SEMICOLON_COMMA_MISSING);
      setNextToken();
  }
  statement();
}

void statement(){
  if(currentToken == identsym) {
      char* intName = tokenNameArr[tokenIndex];

      setNextToken();
      if(currentToken != becomessym) error(IDENT_NFB_EQUALS);
      setNextToken();

      expression();
      //have to store result of expression (i.e. val at top of stack)
      //in symbol at intName
      int identAddr = symbolAddrByName(intName);
      emit(sto, 0, identAddr); // dunno about that l
  }
  else if(currentToken == callsym) {
    // HW3 GRAMMAR DOESN'T ALLOW FOR THIS
      setNextToken();
      if(currentToken != identsym) error(CALL_NFB_ID);
      setNextToken();
  }
  else if(currentToken == beginsym) {
      setNextToken();
      statement();
      while(currentToken == semicolonsym){
          setNextToken();
          statement();
      }
      if(currentToken != endsym) error(END_EXPECTED);
      setNextToken();
  }
  else if(currentToken == ifsym) {
      setNextToken();
      condition();
      if(currentToken != thensym) error(THEN_EXPECTED);
      setNextToken();
      int tempCX = cx;
      emit(jpc, 0, 0);
      statement();
      // this grammar doesn't allow 'else'
      code[tempCX].m = cx;
  }
  else if(currentToken == whilesym) {
      setNextToken();
      int preConditionCX = cx;
      condition();
      if(currentToken != dosym) error(DO_EXPECTED);
      setNextToken();
      int whileCX = cx; // if false, skip to cx yet to be determined
      emit(jpc, 0, 0);
      statement();

      emit(jmp, 0, preConditionCX); // done with do block, so go back to jpc
      code[whileCX].m = cx; // cx is now the instruction after do block

  }
}


void condition(){
  if(currentToken == oddsym) {
      setNextToken();
      expression(); //exp1 at stack[sp]
      emit(opr, 0, odd);
  }
  else{
      expression(); //exp1
      // if currentToken != RELATION
      if(currentToken > 14 && currentToken < 8) error(REL_OP_EXPECTED);
      int relation = currentToken;
      setNextToken();
      expression(); //exp2

      //exp1 is at stack[sp], exp2 is at stack[sp+1]
      switch(relation){
        case eqlsym:
          emit(opr, 0, eql);
          break;
        case neqsym:
          emit(opr, 0, neq);
          break;
        case leqsym:
          emit(opr, 0, leq);
          break;
        case geqsym:
          emit(opr, 0, geq);
          break;
        case gtrsym:
          emit(opr, 0, gtr);
          break;
        case lessym:
          emit(opr, 0, lss);
          break;
      }
  }
}


void expression(){
  int signfactor = 1;
  if(currentToken == plussym || currentToken == minussym){
    signfactor = currentToken == plussym ? 1 : -1;
    setNextToken();
  }
  term();
  // push the -1 or +1 to top of stack
  emit(lit, 0, signfactor);
  // multiply this term with the sign factor
  emit(opr, 0, mul);
  int operation;
  while(currentToken == plussym || currentToken == minussym){
      operation = currentToken == plussym ? add : sub;
      setNextToken();
      term();
      emit(opr, 0, operation);
  }
}


void term(){
  factor();
  while(currentToken == multsym || currentToken == slashsym){
    setNextToken();
    factor();

    // now mult/divide the top two vals on stack
    int operation = currentToken == multsym? mul : divi;
    emit(opr, 0, operation);
  }
}

void factor(){
  if(currentToken == identsym){
    //push val to top of stack
    int variableAddr = symbolAddrByName(tokenNameArr[tokenIndex]);
    emit(lod, 0, variableAddr); //dunno about that l
    setNextToken();
  }
  else if(currentToken == numbersym){
    //push val to top of stack
    emit(lit, 0, atoi(tokenNameArr[tokenIndex])); //dunno about that l
    setNextToken();
  }
  else if(currentToken == lparentsym){
    setNextToken();
    expression();
    if(currentToken != rparentsym) error(RPAREN_EXPECTED);
    setNextToken();
  }
  else error(INVALID_SYM_B4_FACTOR);
}
